/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.objer.objectid.ObjectId;

import org.zhiqim.kernel.util.mutables.MInt;
import org.zhiqim.kernel.util.mutables.MLong;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Hexs;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Streams;
import org.zhiqim.kernel.util.Strings;

/**
 * 通讯工具类
 *
 * @version v1.0.0 @author zouzhigang 2016-11-29 新建与整理
 */
public class GitStreams implements GitConstants
{
    /***********************************************************************************************/
    //读请求信息
    /***********************************************************************************************/
    
    /**
     * 从流中读取长度值，格式为byte[]{'0', '0', '9', 'A'}表示9*16+10
     * 
     * @param in            输入流
     * @return              int长度
     * @throws IOException  IO异常
     */
    public static int readLength(InputStream in) throws IOException
    {
        byte[] lenbs = Streams.getBytes(in, 4);

        try
        {
            int len = Ints.toIntByHex(lenbs);
            if (len != 0 && len < 4)
                throw new NumberFormatException();

            return len;
        }
        catch (NumberFormatException err)
        {
            throw new IOException("非法的包头信息：" + Hexs.toHexString(lenbs));
        }
    }
    
    /**
     * 从流中读取UTF-8字符串，格式为[len,string]，不保留结尾的\n
     * 
     * @param in            输入流
     * @return              字符串，如果结尾是\n，则删除
     * @throws IOException  IO异常
     */
    public static String readString(InputStream in) throws IOException
    {
        int len = readLength(in);
        if (len == 0)
            return null;

        len -= 4;
        if (len == 0)
            return "";

        byte[] bytes = Streams.getBytes(in, len);
        if (bytes[len - 1] == '\n')
            len--;

        return new String(bytes, 0, len, _UTF_8_C_);
    }
    
    /**
     * 从流中读取UTF-8字符串，格式为[len,string]，保留结尾的\n
     * 
     * @param in            输入流
     * @return              字符串，保留结尾的\n
     * @throws IOException  IO异常
     */
    public String readStringRaw(InputStream in) throws IOException
    {
        int len = readLength(in);
        if (len == 0)
            return null;

        len -= 4;
        byte[] bytes = Streams.getBytes(in, len);
        return new String(bytes, 0, len, _UTF_8_C_);
    }
    
    /***********************************************************************************************/
    //读写包头，三部分：包标志、包版本和对象数目
    /***********************************************************************************************/
    
    /**
     * 读包头12个字节，（三部分：包标志、包版本和对象数目）
     * 
     * @param in            输入流
     * @return              对象数目
     * @throws IOException  异常
     */
    public static long readPackHeader(InputStream in) throws IOException
    {
        byte[] buf = Streams.getBytes(in, PACK_HEADER_LEN);

        //1.PACK标志检查
        for (int i=0;i<PACK_SIGNATURE.length;i++){
            Asserts.asserts(buf[i] == PACK_SIGNATURE[i], "传入的不是PACK文件");
        }
        
        //2.PACK版本检查
        long ver = Bytes.BU.getIntUnsigned(buf, 4);
        if (ver != 2)
            throw new IOException("不支持的PACK版本[" + ver + "]");
        
        //3.PACK中对象数目
        return Bytes.BU.getIntUnsigned(buf, 8);
    }
    
    /**
     * 写包头12字节，（三部分：包标志、包版本和对象数目）
     * 
     * @param out           输出流
     * @param objectCount   对象数
     * @throws IOException  IO异常
     */
    public static void writePackHeader(OutputStream out, int objectCount) throws IOException
    {
        byte[] header = new byte[PACK_HEADER_LEN];
        Bytes.putBytes(header, 0, PACK_SIGNATURE);
        Bytes.BU.putInt(header, 4, PACK_VERSION_GENERATED);
        Bytes.BU.putInt(header, 8, objectCount);
        out.write(header, 0, PACK_HEADER_LEN);
    }
    

    /***********************************************************************************************/
    //读写对象头（两部分，对象类型和对象数据展开后的长度）
    // 对象头信息说明
    // 1.第一字节，第1位表示后面是否还有字节是对象头
    // 2.第一字节，第2,3,4位表示对象类型，分别为001-111
    // 3.第一字节，后4位表示对象长度的二进制最后4位，如果len<16，一个字节就够了，第1位为0，否则表示需要第二字节存储长度剩余的位数，第1位为1
    // 4.第二字节后7位，表示长度，如果11位不够，增加第三字节7位，共18位表示长度 ...
    /***********************************************************************************************/
    
    /**
     * 读出对象头信息，包括对象类型和对象长度
     * 
     * @param buf           字节缓冲
     * @param off           缓冲偏移量
     * @param mType         得到对象类型
     * @param mSize         得到对象展开后的长度
     * @throws IOException  读流异常
     */
    public static int readObjectHeader(byte[] buf, int off, MInt mType, MLong mSize) throws IOException
    {
        int b = buf[off++];
        mType.value = (b >> 4) & 7;               //第2,3,4共3位，右移4位，再和0..0111作与运算得到类型
        mSize.value = b & 0x0F;                    //第5,6,7,8共4位，和0..01111作与运算得到长度
        
        int shift = 4;
        while ((b & 0x80) != 0)                 //第1位=1表示后续还有字节
        {//后续字节还有长度                       //第一次左移4位，后续每字节加移7位
            b = buf[off++];
            mSize.value += (b & 0x7F) << shift;    //先和0x7F作与运算，清掉高位上的值，再左移
            shift += 7;
        }
        
        return off;
    }
    
    /**
     * 读出对象头信息，包括对象类型和对象长度
     * 
     * @param in            输入流
     * @param mSize         得到对象展开后的长度
     * @throws IOException  读流异常
     */
    public static int readObjectHeader(InputStream in, MLong mSize) throws IOException
    {
        int b = in.read();
        int type = (b >> 4) & 7;                //第2,3,4共3位，右移4位，再和0..0111作与运算得到类型
        mSize.value = b & 0x0F;                    //第5,6,7,8共4位，和0..01111作与运算得到长度
        
        int shift = 4;
        while ((b & 0x80) != 0)                 //第1位=1表示后续还有字节
        {//后续字节还有长度                       //第一次左移4位，后续每字节加移7位
            b = in.read();
            mSize.value += (b & 0x7F) << shift;    //先和0x7F作与运算，清掉高位上的值，再左移
            shift += 7;
        }
        
        return type;
    }
    
    /**
     * 写入对象头标志，包括对象类型和对象长度
     * 
     * @param type      对象类型
     * @param size      对象展开后的长度
     * @return          返回对象头字节数组
     */
    public static final byte[] writeObjectHeader(int type, long size)
    {
      //计算长度，第一字节4位，如果不够后面每字节7位
        int count = 1;long temp = size;temp >>>= 4;
        while (temp > 0){
            count++;temp >>>= 7;
        }
        
        byte[] header = new byte[count];
      
        //第一字节三部分
        header[0] = (byte)((count==1?0:0x80) | (type << 4) | (size & 0x0F));

        //第二字节及后面字节，两部分
        size = size >>>= 4;
        for (int i=2;i<=count;i++,size>>>=7)
        {
            header[i-1] = (byte)((count==i?0:0x80) | (size & 0x7F));
        }
        
        return header;
    }
    
    /**
     * 写入对象头标志，包括对象类型和对象长度
     * 
     * @param out       输出流
     * @param type      对象类型
     * @param size      对象展开后的长度
     */
    public static final void writeObjectHeader(OutputStream out, int type, long size) throws IOException
    {
        out.write(writeObjectHeader(type, size));
    }
    
    /***********************************************************************************************/
    //读增量对象编号（OBJ_REF_DELTA）&（OBJ_OFS_DELTA）
    /***********************************************************************************************/
    
    /**
     * 读出对象增量基础对象编号
     * 
     * @param in            输入流
     * @return              引用对象编号
     * @throws IOException  异常
     */
    public static ObjectId readObjectId(InputStream in) throws IOException
    {
        return ObjectId.parse(Streams.getBytes(in, LEN_OBJECT_ID));
    }
    
    /**
     * 读出对象增量偏移值
     * 
     * @param in            输入流
     * @return              偏移值
     * @throws IOException  异常
     */
    public static long readObjectOfsDelta(InputStream in) throws IOException
    {
        int c = in.read();
        long ofs = c & 0x7F;
        while ((c & 0x80) != 0)     //第1位=1表示后续还有字节
        {
            ofs += 1;
            c = in.read();
            ofs <<= 7;
            ofs += (c & 0x7F);
        }
        
        return ofs;
    }
    
    /***********************************************************************************************/
    //写入流
    /***********************************************************************************************/
    
    /**
     * 写入边频带错误字符串，采用UTF-8编码
     * 
     * @param s                 字符串
     * @throws IOException      IO异常
     */
    public static void writeSideBandError(OutputStream out, final String s) throws IOException
    {
        byte[] packet = Bytes.BU.toBytes(s);
        out.write(toBytesHex4(packet.length + SB_HDR_SIZE));
        out.write(SB_CH_ERROR);
        out.write(packet);
    }
    
    /**
     * 写入边频带进度字符串，采用UTF-8编码
     * 
     * @param s                 字符串
     * @throws IOException      IO异常
     */
    public static void writeSideBandProgress(OutputStream out, final String s) throws IOException
    {
        byte[] packet = Bytes.BU.toBytes(s);
        out.write(toBytesHex4(packet.length + SB_HDR_SIZE));
        out.write(SB_CH_PROGRESS);
        out.write(packet);
    }
    
    /**
     * 写入字符串，采用UTF-8编码
     * 
     * @param s                 字符串
     * @throws IOException      IO异常
     */
    public static void writeString(OutputStream out, final String s) throws IOException
    {
        writePacket(out, Bytes.BU.toBytes(s));
    }

    /**
     * 写入byte数组
     * 
     * @param packet            byte包
     * @throws IOException      IO异常
     */
    public static void writePacket(OutputStream out, final byte[] packet) throws IOException
    {
        out.write(toBytesHex4(packet.length + 4));
        out.write(packet);
    }
    
    /** 转化为4字节（最大65535）的字节数组 */
    public static byte[] toBytesHex4(int i)
    {
        String hex = Integer.toString(i, 16).toLowerCase();
        return Bytes.toBytesASCII(Strings.prefixZero(hex, 4));
    }
    
    /**
     * 写入包结束标志（0000），表示长度为0后面没有数据
     * 
     * @throws IOException  IO异常
     */
    public static void writeEnd(OutputStream out) throws IOException
    {
        out.write(END);
    }
}
